<?php
/**
 * Crc CMS Grid
 *
 * @category   Crc
 * @package    Grid
 * @author     Moldovan Calin <mcalin45@gmail.com>
 * @author     Vlad Razvan <razvanvlad1982@yahoo.com>
 * @copyright  Copyright (c) 2012
 * @version    $Id:  $
 */

namespace Crc;

use Crc\Entity\AbstractEntity,
    Crc\Tools\Doctrine2,
    Doctrine\Common\Collections AS Collections,
    Doctrine\ORM\QueryBuilder;


/**
 * Class for the admin Grid.
 *
 * @category   Crc
 * @package    Grid
 * @author     Moldovan Calin <mcalin45@gmail.com>
 * @author     Vlad Razvan <razvanvlad1982@yahoo.com>
 * @copyright  Copyright (c) 2013
 */
class Grid extends Doctrine2
{
    /**
     * Doctrine Entity Manager
     *
     * @var Doctrine\ORM\EntityManager
     **/
    protected $_entityManager = NULL;

    /**
     * Doctrine Query Builder
     *
     * @var Doctrine\ORM\QueryBuilder
     **/
    protected $_queryBuilder = NULL;

    /**
     * The entity from with the grid will generate
     *
     * @var Crc\Entity\AbstractEntity
     **/
    protected $_entity;

    /**
     * The list of filters for the entity
     * Are generated from the entity annotations
     *
     * @var Doctrine\Common\Collections\ArrayCollection
     **/
    protected $_filters;

    /**
     * The list of columns for the entity
     * Are generated from the entity annotations
     *
     * @var Doctrine\Common\Collections\ArrayCollection
     **/
    protected $_columns;

    /**
     * The list of quick actions for the entity
     * Are generated from the entity annotations
     *
     * @var Doctrine\Common\Collections\ArrayCollection
     **/
    protected $_quickActions;

    /**
     * The metadata for the entity
     *
     * @var Doctrine\ORM\Mapping\ClassMetadata
     **/
    protected $_entityMetadata;


    /**
     * Construct the grid for the selected entity
     *
     * @param  string $entity
     * @return void
     */
    public function __construct($entity)
    {
        $doctrine = \Zend_Registry::get('doctrine');
        $this->_entityManager = $doctrine->getEntityManager();
        $this->setEntity($entity)->setMetadata();
    }

    /**
     * Set the selected entity
     *
     * @param  string $entity
     * @return Grid
     */
    protected function setEntity($entity)
    {
        $this->_entity = $entity;
        return $this;
    }

    /**
     * Return the selected entity
     *
     * @return string
     */
    public function getEntity()
    {
        return $this->_entity;
    }

    /**
     * Set the metadata for entity
     *
     * @return void
     */
    protected function setMetadata()
    {
        $this->_entityMetadata = $this->_entityManager->getClassMetadata($this->_entity);
    }

    /**
     * Return the query builder object
     * If the query builder object has not been previously set up then it
     * creates a default select for the current entity with default order by
     *
     * @return Doctrine\ORM\QueryBuilder
     */
    protected function getQueryBuilder()
    {
        if ($this->_queryBuilder === NULL) {
            $queryBuilder = $this->_entityManager->createQueryBuilder();
            $queryBuilder->select('ce')
                         ->from($this->_entity, 'ce')
                         ->orderBy('ce.' . $this->getOrderBy(), $this->getOrderType());
            $this->_queryBuilder = $queryBuilder;
        }
        return $this->_queryBuilder;
    }

    public function getQuickActions()
    {
        if (!$this->_quickActions) {
            $quickActions = new Collections\ArrayCollection();
            $class = $this->_entityMetadata->getReflectionClass();
            $annotations = self::_getAnnotationReader()->getClassAnnotations($class);
            foreach($annotations as $annotation) {
                if ($annotation instanceof \Crc\Annotations\Grid\QuickActions) {
                    $quickActions = $annotation->actions;
                }
            }
            $this->_quickActions = $quickActions;
        }
        return $this->_quickActions;
    }

    /**
     * Returns a list of column objects
     * It builds the list from the annotations
     *
     * @return Doctrine\Common\Collections\ArrayCollection
     */
    protected function generateColumns()
    {
        $sessionGrid = new \Zend_Session_Namespace('Grid_' . $this->_entity);
        $this->_columns = $sessionGrid->columns;
        if (!$this->_columns) {
            $columns = new Collections\ArrayCollection();
            $contor = 0;
            foreach($this->_entityMetadata->fieldMappings as $name => $data) {
                $property = $this->_entityMetadata->getReflectionProperty($name);
                $annotations = self::_getPropertyAnnotation($property, 'Crc\Annotations\Grid\Column');
                $annotation = array_shift($annotations);
                if ($annotation->show) {
                    $contor++;
                    $column = new \Crc\Grid\Column();
                    $column->setFromAnnotation($annotation)
                           ->setField($name)
                           ->setSortOrder($contor);
                    if ($annotation->default)
                        $column->setVisible();
                    $columns->add($column);
                }
            }
            foreach($this->_entityMetadata->associationMappings as $name => $data) {
                $property = $this->_entityMetadata->getReflectionProperty($name);
                $annotations = self::_getPropertyAnnotation($property, 'Crc\Annotations\Grid\Column');
                $annotation = array_shift($annotations);
                if ($annotation->show) {
                    $contor++;
                    $column = new \Crc\Grid\Column();
                    $column->setFromAnnotation($annotation)
                           ->setField($name)
                           ->setSortOrder($contor);
                    if ($annotation->default)
                        $column->setVisible();
                    $columns->add($column);
                }
            }
            $this->_columns = $columns;
            $this->saveColumns();
        }
        return $this->_columns;
    }

    /**
     * Saves to session the list of columns
     *
     * @return void
     */
    protected function saveColumns()
    {
        $sessionGrid = new \Zend_Session_Namespace('Grid_' . $this->_entity);
        $sessionGrid->columns = serialize($this->_columns);
    }

    /**
     * Returns a list of column objects
     *
     * @return Doctrine\Common\Collections\ArrayCollection
     */
    public function getColumns()
    {
        if ($this->_columns === NULL) {
            $sessionGrid = new \Zend_Session_Namespace('Grid_' . $this->_entity);
            $columns = unserialize($sessionGrid->columns);
            if (!$columns) {
                $columns = $this->generateColumns();
                $sessionGrid->columns = serialize($columns);
            }
            $this->_columns = $columns;
        }
        return $this->_columns;
    }

    /**
     * Returns a list of column objects that are not showned in the listing
     *
     * @return Doctrine\Common\Collections\ArrayCollection
     */
    public function getInvisibleColumns()
    {
        $columns = $this->getColumns();
        $invisibleColumns = new Collections\ArrayCollection();
        foreach ($columns as $column) {
            if (!$column->getVisible()) {
                $invisibleColumns->add($column);
            }
        }
        return $invisibleColumns;
    }

    /**
     * Returns a list of column objects that are showned in the listing
     *
     * @return ArrayObject
     */
    public function getVisibleColumns()
    {
        $columns = $this->getColumns();
        $visibleColumns = new \ArrayObject();
        foreach ($columns as $column) {
            if ($column->getVisible()) {
                $visibleColumns->offsetSet($column->getSortOrder(), $column);
            }
        }
        $visibleColumns->ksort();
        return $visibleColumns;
    }

    /**
     * Set a list of columns to be visible in the listing
     * It also sets the order in which the columns are displayed
     *
     * @param array $selectedColumns An array containing only the selected fields for display
     * @return Grid
     */
    public function setVisibleColumns($selectedColumns)
    {
        if (is_array($selectedColumns) && count($selectedColumns) > 0) {
            $columns = $this->getColumns();
            foreach($columns as $column) {
                if (in_array($column->getField(), $selectedColumns)) {
                    $contor = array_search($column->getField(), $selectedColumns);
                    $contor++;
                    $column->setVisible()
                           ->setSortOrder($contor);
                } else {
                    $column->setVisible(false);
                }
            }
        } else {
            $this->resetColumns();
        }
        $this->saveColumns();
        return $this;
    }

    /**
     * Returns a list of filter objects
     * It builds the list from the annotations
     *
     * @return Doctrine\Common\Collections\ArrayCollection
     */
    protected function generateFilters()
    {
        $filters = new Collections\ArrayCollection();
        foreach($this->_entityMetadata->fieldMappings as $name => $data) {
            $property = $this->_entityMetadata->getReflectionProperty($name);
            $annotations = self::_getPropertyAnnotation($property, 'Crc\Annotations\Grid\Filter');
            $annotation = array_shift($annotations);
            if (!empty($annotation->name)) {
                $className = '\Crc\Grid\Filter\\' . ucfirst($annotation->name);
                $filter = new $className($name);
                $filter->setLabel($annotation->label);
                $filters->add($filter);
            }
        }

        foreach($this->_entityMetadata->associationMappings as $name => $data) {
            $property = $this->_entityMetadata->getReflectionProperty($name);
            $annotations = self::_getPropertyAnnotation($property, 'Crc\Annotations\Grid\Filter');
            $annotation = array_shift($annotations);
            if (!empty($annotation->name)) {
                $className = '\Crc\Grid\Filter\\' . ucfirst($annotation->name);
                $filter = new $className($name);
                $filter->setLabel($annotation->label);
                if ($annotation->entity) {
                    $filter->setEntity($annotation->entity)
                           ->setValueField($annotation->valueField)
                           ->setLabelField($annotation->labelField);
                }
                $filters->add($filter);
            }
        }
        return $filters;
    }

    /**
     * Saves to session the list of filters
     *
     * @return void
     */
    protected function saveFilters()
    {
        $sessionGrid = new \Zend_Session_Namespace('Grid_' . $this->_entity);
        $sessionGrid->filters = serialize($this->_filters);
    }

    /**
     * Returns a list of filter objects
     *
     * @return Doctrine\Common\Collections\ArrayCollection
     */
    public function getFilters()
    {
        if ($this->_filters === NULL) {
            $sessionGrid = new \Zend_Session_Namespace('Grid_' . $this->_entity);
            $filters = unserialize($sessionGrid->filters);
            if (!$filters) {
                $filters = $this->generateFilters();
                $sessionGrid->filters = serialize($filters);
            }
            $this->_filters = $filters;
        }
        return $this->_filters;
    }

    /**
     * Sets a filter as selected. If the $filterName is not a valid filter name
     * or the $operatorName is not a valid operator name it throws an exception
     * If all is ok set the matching filter as selected and the operator as
     * selected and then is saved the filters
     *
     * @param string $filterName    The name of the filter
     * @param string $operatorName  The name of the operator
     * @param string $value         The selected value
     * @throws Exception            When the filterName is not a valid filter name
     * @throws Exception            When the operatorName is not a valid operator name
     * @return Grid
     */
    public function selectFilter($filterName, $operatorName, $value)
    {
        $filter = $this->getByName($filterName);
        if (!$filter) {
            throw new \Exception("Invalid selected filter '".$filterName."'");
        }

        $operator = $filter->getOperatorByName($operatorName);
        if (!$operator) {
            throw new \Exception("Invalid selected operator '".$filterName."' for filter '" . $filter->getField() . "'");
        }

        $operator->setSelected()
                 ->setValue($value)
                 ->setField($filter->getField());
        $filter->setSelected()
               ->setOperator($operator);
        $this->saveFilters();
        return $this;
    }

    /**
     * Resets the filters to the default state
     *
     * @return void
     */
    public function resetFilters() {
        $sessionGrid = new \Zend_Session_Namespace('Grid_' . $this->_entity);
        unset($sessionGrid->filters);
        $filters = $this->generateFilters();
        $sessionGrid->filters = serialize($filters);
    }

    /**
     * Resets the columns to the default state
     *
     * @return void
     */
    public function resetColumns() {
        $sessionGrid = new \Zend_Session_Namespace('Grid_' . $this->_entity);
        unset($sessionGrid->columns);
        $columns = $this->generateColumns();
        $sessionGrid->columns = serialize($columns);
    }

    /**
     * Prepares the Query Builder and then processes all the selected filters and return the Query Builder for the
     * paginator
     *
     * @return Doctrine\ORM\QueryBuilder
     */
    public function processGrid()
    {
        $queryBuilder= $this->getQueryBuilder();
        $queryBuilder = $this->processFilters($queryBuilder);
        return $queryBuilder;
    }

    /**
     * Processes all the selected filters and return the Query Builder for the
     * paginator
     *
     * @param Doctrine\ORM\QueryBuilder $queryBuilder    The Query Builder object
     * @return Doctrine\ORM\QueryBuilder
     */
    protected function processFilters(QueryBuilder $queryBuilder)
    {
        foreach($this->getFilters() as $filter) {
            if ($filter->isSelected()) {
                $queryBuilder = $filter->processFilter($queryBuilder);
            }
        }
        return $queryBuilder;
    }

    /**
     * Find an operator by name
     *
     * @param string  $name
     * @return  Crc\Grid\Filter | boolean
     */
    protected function getByName($field)
    {
        if (trim($field) == "") {
            return false;
        }

        foreach ($this->getFilters() as $filter) {
            if ($filter->getField() == $field) {
                return $filter;
            }
        }
        return false;
    }

    public function setOrder($field)
    {
        $sessionGrid = new \Zend_Session_Namespace('Grid_' . $this->_entity);
        if ($sessionGrid->orderByField == $field) {
            $sessionGrid->orderByType = ($sessionGrid->orderByType == 'ASC') ? 'DESC' : 'ASC';
        } else {
            $sessionGrid->orderByField = $field;
            $sessionGrid->orderByType = 'ASC';
        }
    }

    public function getOrderBy()
    {
        $sessionGrid = new \Zend_Session_Namespace('Grid_' . $this->_entity);
        if ($sessionGrid->orderByField) {
            return $sessionGrid->orderByField;
        } else {
            $columns = $this->getColumns();
            foreach ($columns as  $column) {
                if ($column->isDefaultOrder()) {
                    return $column->getField();
                }
            }
        }
    }

    public function getOrderType()
    {
        $sessionGrid = new \Zend_Session_Namespace('Grid_' . $this->_entity);
        if ($sessionGrid->orderByType) {
            return $sessionGrid->orderByType;
        } else {
            $columns = $this->getColumns();
            foreach ($columns as  $column) {
                if ($column->isDefaultOrder()) {
                    return $column->getOrder();
                }
            }
        }
    }


    public function setQuickAction($action, $params) {
        $class = explode('\\', $this->_entity);
        $entity = array_pop($class);
        $className = implode('\\', $class) . '\QuickActions\\' . $entity;
        $qa = new $className();
        $qa->setEntity($this->_entity)
           ->$action($params);
    }
}